package webmail;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.URLDecoder;
import java.text.ParseException;
import java.util.Calendar;
import java.util.StringTokenizer;
import org.xbill.DNS.*;

public class EmailMessage implements ActionListener {

    public String senderMailAccount = null;
    public String receiverMailAccount = null;
    public String senderMailServer = null;
    public String receiverMailServer = null;
    public String relaySmtpServer = null;
    public String subject = null;
    public Calendar receivedTime = null;
    public Calendar deliveryTime = null;
    public int delay = 0;
    public boolean sent = false;
    
    private String data = null;
    private String[] serverReply = new String[7];
    private int serverReplySize = -1;

    public EmailMessage(String args) throws ParseException, UnsupportedEncodingException {
        StringTokenizer st = new StringTokenizer(args,"&");

        String[] spl = new String[2];

        while (st.hasMoreTokens())
        {
            spl = st.nextToken().split("=");
            spl[0] = URLDecoder.decode(spl[0],"ISO-8859-15");
            if (spl.length>1)
            {
                spl[1] = URLDecoder.decode(spl[1],"ISO-8859-15");
            }
            if (spl[0].matches("^from$"))
            {
                //parse and validate sender mail address
                if (spl.length>1)
                {
                    if (!(spl[1].matches("^[a-zA-Z0-9.+%_-]+@(?:[a-zA-Z0-9]+\\.)+[a-zA-Z]{1,63}$")))
                    {
                        throw new ParseException("Invalid sender email address",0);
                    }
                    String[] senderMailAddress = new String[2];
                    senderMailAddress = spl[1].split("@");
                    senderMailAccount = senderMailAddress[0];
                    senderMailServer = senderMailAddress[1];
                }
                else
                {
                    throw new ParseException("Sender email address empty",0);
                }
            }

            if (spl[0].matches("^to$"))
            {
                //parse and validate receiver mail address
                if (spl.length>1)
                {
                    if (!(spl[1].matches("^[a-zA-Z0-9.+%_-]+@(?:[a-zA-Z0-9]+\\.)+[a-zA-Z]{1,63}+$")))
                    {
                        throw new ParseException("Invalid receiver email address!",0);
                    }
                    String[] receiverMailAddress = new String[2];
                    receiverMailAddress = spl[1].split("@");
                    receiverMailAccount = receiverMailAddress[0];
                    receiverMailServer = receiverMailAddress[1];
                }
                else
                {
                    throw new ParseException("Receiver email address empty",0);
                }
            }

            if (spl[0].matches("^subject$"))
            {
                if (spl.length>1)
                {
                    subject = spl[1];
                }
                else
                {
                    throw new ParseException("Subject field is empty",0);
                }
            }

            if (spl[0].matches("^message$"))
            {
                if (spl.length>1)
                {
                    data = spl[1];
                }
                else
                {
                    throw new ParseException("The body of the email is empty",0);
                }
            }

            if (spl[0].matches("^delay$"))
            {
                if (spl.length>1)
                {
                    delay = Integer.parseInt(spl[1]);
                    if (delay<0)
                    {
                        throw new ParseException("Please enter a non-negative delay",0);
                    }
                }
                else
                {
                    throw new ParseException("Please enter a delay",0);
                }
            }
        }

        receivedTime = Calendar.getInstance();
        deliveryTime = Calendar.getInstance();
        deliveryTime.add(Calendar.SECOND,delay);
    }

    public void SendMessage() throws Exception {

        Socket sendSocket = new Socket();
        int timeout = 10000; //timeout in milliseconds

        try
        {
            relaySmtpServer = MXLookup(receiverMailServer);
            String relaySmtpServerIP = DnsLookup(relaySmtpServer);
            sendSocket.connect(new InetSocketAddress(relaySmtpServerIP,25),timeout);
        }

        catch (Exception ex)
        {
            ex.printStackTrace();
            Main.messageCounter -= 1;
            serverReply[0] = "Probably a DNS error";
            serverReplySize += 1;
            this.SendReplyToSender();
            throw new ParseException("DNS lookup went wrong: "+receiverMailServer,0);
        }
        
        DataOutputStream toSmtpServer = new DataOutputStream(sendSocket.getOutputStream());
        DataInputStream fromSmtpServer = new DataInputStream(sendSocket.getInputStream());
        
        serverReply[0] = fromSmtpServer.readLine();
        serverReplySize += 1;

        if (!(serverReply[0].matches("^((220)|(250)|(354)|(221)).*")))
        {
            Main.messageCounter -= 1;
            this.SendReplyToSender();
            throw new ParseException("Error: server reply was "+serverReply[0],0);
        }

        toSmtpServer.writeBytes("HELO "+relaySmtpServer+"\r\n");
        serverReply[1] = fromSmtpServer.readLine();
        serverReplySize += 1;

        if (!(serverReply[1].matches("^((220)|(250)|(354)|(221)).*")))
        {
            Main.messageCounter -= 1;
            this.SendReplyToSender();
            throw new ParseException("Error: server reply was"+serverReply[1],0);
        }

        toSmtpServer.writeBytes("MAIL FROM: <"+senderMailAccount+"@"+senderMailServer+">\r\n");
        serverReply[2] = fromSmtpServer.readLine();
        serverReplySize += 1;

        if (!(serverReply[2].matches("^((220)|(250)|(354)|(221)).*")))
        {
            Main.messageCounter -= 1;
            this.SendReplyToSender();
            throw new ParseException("Error: server reply was "+serverReply[2],0);
        }

        toSmtpServer.writeBytes("RCPT TO: <"+receiverMailAccount+"@"+receiverMailServer+">\r\n");
        serverReply[3] = fromSmtpServer.readLine();
        serverReplySize += 1;

        if (!(serverReply[3].matches("^((220)|(250)|(354)|(221)).*")))
        {
            Main.messageCounter -= 1;
            this.SendReplyToSender();
            throw new ParseException("Error: server reply was "+serverReply[3],0);
        }

        toSmtpServer.writeBytes("DATA\r\n");
        serverReply[4] = fromSmtpServer.readLine();
        serverReplySize += 1;

        if (!(serverReply[4].matches("^((220)|(250)|(354)|(221)).*")))
        {
            Main.messageCounter -= 1;
            this.SendReplyToSender();
            throw new ParseException("Error: server reply was "+serverReply[4],0);
        }

        toSmtpServer.writeBytes("MIME-Version: 1.0\r\n");
        toSmtpServer.writeBytes("Content-Type: text/plain\r\n");
        toSmtpServer.writeBytes("Content-Transfer-Encoding: quoted-printable\r\n");
        toSmtpServer.writeBytes("Subject: =?iso-8859-15?Q?"+ToMimeQ(subject)+"?=\r\n");
        toSmtpServer.writeBytes("from: <"+senderMailAccount+"@"+senderMailServer+">\r\n");
        toSmtpServer.writeBytes("to: "+receiverMailAccount+"@"+receiverMailServer+"\r\n");
        toSmtpServer.writeBytes(ToQuotedPrintable(data));
        toSmtpServer.writeBytes("\r\n.\r\n");

        serverReply[5] = fromSmtpServer.readLine();
        serverReplySize += 1;

        if (!(serverReply[5].matches("^((220)|(250)|(354)|(221)).*")))
        {
            Main.messageCounter -= 1;
            this.SendReplyToSender();
            throw new ParseException("Error: server reply was "+serverReply[5],0);
        }

        toSmtpServer.writeBytes("QUIT\r\n");
        serverReply[6] = fromSmtpServer.readLine();
        serverReplySize += 1;

        if (!(serverReply[6].matches("^((220)|(250)|(354)|(221)).*")))
        {
            Main.messageCounter -= 1;
            this.SendReplyToSender();
            throw new ParseException("Error: server reply was"+serverReply[6],0);
        }

        toSmtpServer.close();
        fromSmtpServer.close();
        sendSocket.close();
        sent = true;
        Main.messageCounter -= 1;
    }

    private String MXLookup(String args) throws Exception  {
        Record[] records = null;
        records = new Lookup(args,Type.MX).run();
        MXRecord mx = null;
        if (records != null)
        {
            mx = (MXRecord) records[0];
        }
        else
        {
            throw new ParseException("MX lookup did not return any results",0);
        }
        return mx.getTarget().toString();
    }

    private String ToQuotedPrintable(String args) throws Exception {

        String res = "";
        int curr = '0';
        char [] c = args.toCharArray();

        for (int i=0; i<args.length(); i++)
        {
            curr = c[i];
            switch (curr)
            {
                case 10:
                {
                    if (c[i+1] == 13)
                    {
                        res = res+"\r";
                        break;
                    }
                }
                default:
                {
                    if ((curr<33) || (curr==46) || (curr==61) || (curr>126))
                    {
                        String hi = Integer.toHexString(curr/16).toUpperCase();
                        String lo = Integer.toHexString(curr % 16).toUpperCase();
                        res = res+"="+hi+lo;
                    }
                    else
                    {
                        res = res+c[i];
                    }
                }
            }
        }
        return res;
    }

    private String ToMimeQ(String args) throws Exception {
        String res = "";
        int curr = '0';
        char [] c = args.toCharArray();

        for (int i=0; i<args.length(); i++)
        {
            curr = c[i];
            switch (curr)
            {
                case 10:
                {
                    res = res+"\r";
                    break;
                }
                case 13:
                {
                    res = res+"\r";
                    break;
                }
                case 20:
                {
                    res = res+"_";
                    break;
                }

                default:
                {
                    if ((curr<33) || (curr==95) || (curr==63) || (curr>126))
                    {
                        String hi = Integer.toHexString(curr/16).toUpperCase();
                        String lo = Integer.toHexString(curr % 16).toUpperCase();
                        res = res+"="+hi+lo;
                    }
                    else
                    {
                        res = res+c[i];
                    }
                }
            }
        }
        return res;
    }

    private String DnsLookup(String args) throws Exception {
        return(InetAddress.getByName(args).getHostAddress());
    }

    private String ValidateServerReply() throws Exception {
        String replyLine = "Message with subject '"+ToQuotedPrintable(subject)+"' sent successfully!";
        int i;
        if (serverReplySize>=0)
        {
            for (i=0; i<=serverReplySize; i++)
            {
                if (!(serverReply[i].matches("^((220)|(250)|(354)|(221)).*")))
                {
                    replyLine = "There was an error while delivering message with subject '"+ToQuotedPrintable(subject)+"':\r\nError code was:\r\n"+serverReply[i];
                }
            }
        }
        else
        {
            replyLine = "No reply from server";
        }
        return replyLine;
        
    }

    private void SendReplyToSender() throws Exception {

        Socket sendSocket = new Socket();
        int timeout = 10000;
        String senderSmtpServerIP = DnsLookup(MXLookup(senderMailServer));
        sendSocket.connect(new InetSocketAddress(senderSmtpServerIP,25),timeout);

        DataOutputStream toSmtpServer = new DataOutputStream(sendSocket.getOutputStream());
        DataInputStream fromSmtpServer = new DataInputStream(sendSocket.getInputStream());

        toSmtpServer.writeBytes("HELO "+MXLookup(senderMailServer)+"\r\n");
        System.out.println(fromSmtpServer.readLine());
        toSmtpServer.writeBytes("MAIL FROM: <noreply@ik2213.lab>\r\n");
        System.out.println(fromSmtpServer.readLine());
        toSmtpServer.writeBytes("RCPT TO: <"+senderMailAccount+"@"+senderMailServer+">\r\n");
        System.out.println(fromSmtpServer.readLine());
        toSmtpServer.writeBytes("DATA\r\n");
        System.out.println(fromSmtpServer.readLine());
        toSmtpServer.writeBytes("MIME-Version: 1.0\r\n");
        toSmtpServer.writeBytes("Content-Type: text/plain\r\n");
        toSmtpServer.writeBytes("Content-Transfer-Encoding: quoted-printable\r\n");
        toSmtpServer.writeBytes("Subject: =?iso-8859-15?Q?"+ToMimeQ("Delayed message delivery")+"?=\r\n");
        toSmtpServer.writeBytes("from: <noreply@ik2213.lab>\r\n");
        toSmtpServer.writeBytes("to: "+senderMailAccount+"@"+senderMailServer+"\r\n");

        toSmtpServer.writeBytes(ValidateServerReply());
        toSmtpServer.writeBytes("\r\n.\r\n");
        System.out.println(fromSmtpServer.readLine());
        toSmtpServer.writeBytes("QUIT\r\n");
        System.out.println(fromSmtpServer.readLine());
        toSmtpServer.close();
        sendSocket.close();
    }

    public void actionPerformed(ActionEvent ae){
        try
        {
            this.SendMessage();
        }
        catch (Exception ex)
        {
            Main.ReturnPage(ex.toString());
        }
    }
}
